# =========================
# 这些库差什么安装什么即可
# =========================
# 系统库
import os
import requests
import json
import time
import subprocess
import shutil
from multiprocessing import Process
# 进度条库
from tqdm import tqdm
# selenium相关的库
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC

# 下载浏览器的驱动
service = Service(executable_path=ChromeDriverManager().install())

# 等待元素出现
def wait_elm(driver: webdriver, timeout: int, xpath: str):
    """
    等待元素出现
    :param driver:浏览器实例
    :param timeout:等待时间
    :param xpath:xpath地址
    :return:
    """
    try:
        WebDriverWait(driver, timeout).until(EC.presence_of_element_located((By.XPATH, xpath)))
        return True
    except Exception as e:
        # print(f'未查询到该元素:{xpath}---msg:{e}')
        print(f'未查询到该元素:{xpath}')
        return False

# 显示进度的下载
def file_download(url: str, filename: str) -> str:
    if not os.path.isfile(filename):
        # 获取文件的总大小
        response = requests.head(url)
        file_size = int(response.headers.get('content-length', 0))

        # 使用tqdm显示进度
        with requests.get(url, stream=True) as r, open(filename, 'wb+') as fd:
            for chunk in tqdm(r.iter_content(chunk_size=1024), total=file_size // 1024 + (file_size % 1024 > 0)):
                # 如果有chunk（因为可能最后一块不足1024字节）
                if chunk:
                    fd.write(chunk)
                    # 每隔2秒打印一次进度
                    # time.sleep(2)
    return filename

# 浏览器自动化上传抖音视频
def douyin_publish_server(task_config, driver):
    try:
        # 拼接mp4文件存放地址
        filename = "C:/videos/" + task_config['video_url'].split('/')[-1]
        # 解析cookie数据
        cooklist = json.loads(task_config["account_cookie"])
        # 控制浏览器访问一下抖音后台然后把cookie清除干净
        driver.get("https://creator.douyin.com/aweme/v1/creator/user/info/")
        driver.delete_all_cookies()
        # 重新设置cookie
        for i in cooklist:
            driver.add_cookie(i)
        # 直接访问视频上传页面
        driver.get("https://creator.douyin.com/creator-micro/content/upload")
        # 等待5秒
        time.sleep(5)
        # 判断cookie是否有效
        if driver.execute_script("return document.location.href") == "https://creator.douyin.com/":
            print("cookie无效")
            return False
        
        # 下载视频文件到本地
        file_download(task_config['video_url'], filename)

        # 上传视频
        upload_xpath = '//input[@class="upload-btn-input--1NeEX"]'
        if wait_elm(driver, 60, upload_xpath):
            driver.find_element(By.XPATH, upload_xpath).send_keys(filename)
        else:
            print("没有找到上传按钮")
            return False

        # 输入标题
        if task_config["title"]:
            title_xpath = '//div[@class="semi-input-wrapper semiInput--1qQVT semi-input-wrapper__with-suffix semi-input-wrapper-default"]/input[@class="semi-input semi-input-default"]'
            if wait_elm(driver, 15, title_xpath):
                driver.find_element(By.XPATH, title_xpath).send_keys(task_config["title"])

        # 输入简介
        if task_config["intro"]:
            intro_xpath = '//div[@class="zone-container editor-kit-container editor editor-comp-publish notranslate chrome window chrome88"]'
            if wait_elm(driver, 15, intro_xpath):
                driver.find_element(By.XPATH, intro_xpath).send_keys(task_config["intro"])
                
        # 滑动屏幕，模拟一下人的操作，这里的500参数最好通过设置成200-1000的随机数
        driver.execute_script('document.documentElement.scrollTop = 500')

        # 判断是否有定位地址
        if task_config["poi_name"]:
            # 点击地址输入框
            orientation_xpath = '//div[@class="anchor-container--2MRii"]//span[@class="semi-select-selection-text semi-select-selection-placeholder"]'
            if wait_elm(driver, 15, orientation_xpath):
                driver.find_element(By.XPATH, orientation_xpath).click()
            time.sleep(2)
            # 在输入框中输入地址
            add_xpath = '//div[@class="anchor-container--2MRii"]//input[@class="semi-input semi-input-default"]'
            if wait_elm(driver, 15, add_xpath):
                driver.find_element(By.XPATH, add_xpath).send_keys(task_config["poi_name"])
            time.sleep(5)
            # 选择第一个地址
            select_add_xpath = '//div[@class="semi-select-option-list semi-select-option-list-chosen"]/div[1]'
            if wait_elm(driver, 15, select_add_xpath):
                js = r'document.getElementsByClassName("semi-select-option-list semi-select-option-list-chosen")[0].getElementsByTagName("div")[0].click();'
                driver.execute_script(js)
            time.sleep(5)
            
        # 判断视频是否上传成功
        if wait_elm(driver, 60, "//div[contains(text(), '视频剪辑')]"):
            print('视频上传成功')
        else:
            print("视频上传失败，有可能是网络问题，此处请自行新增处理逻辑")
            return False

        # 发布按钮的xpath
        putout_xpath = '//button[@class="button--1SZwR primary--1AMXd fixed--3rEwh"]'
        # 判断一下发布按钮是否存在
        if wait_elm(driver, 15, putout_xpath):
            # 点击一下发布按钮
            driver.find_element(By.XPATH, putout_xpath).click()
            # 点击发布按钮后就一直循环判断是否有跳转到视频列表的url
            while True:
                # 检测是否发布成功
                if driver.execute_script("return document.location.href") == "https://creator.douyin.com/creator-micro/content/manage":
                    print(f"URL判断是否发布成功：已成功跳转到视频列表。")
                    break
                else:
                    # 判断是否报了验证码
                    if wait_elm(driver, 5, "//div[contains(text(), '手机号验证')]"):
                        print('发布失败：手机号验证，请使用此账号登录创作者中心后台人工发布一次视频。')
                        return False
                # 如果又没跳转到视频列表又没有报验证码就再继续点击发布按钮
                time.sleep(3)
                try:
                    driver.find_element(By.XPATH, putout_xpath).click()
                except:
                    break
        # 如果跳出了循环说明发布成功了，清除一下浏览器的cookie
        driver.delete_all_cookies()
        # 等待下一个任务进来
    
    except Exception as e:
        # 任务失败
        print(e)
        driver.delete_all_cookies()
    finally:
        try:
            driver.delete_all_cookies()
            os.remove(filename)
        except:
            pass

# 浏览器自动化上传小红书视频
def xhs_publish_server(task_config, driver):
    xhs_url = "https://creator.xiaohongshu.com/login"
    pub_url = "https://creator.xiaohongshu.com/publish/publish"
    manage_url = "https://creator.xiaohongshu.com/publish/publish?source="
    upload_xpath = '//input[@class="upload-input"]'
    title_xpath = '//input[@class="c-input_inner"]'
    intro_xpath = '//*[@id="post-textarea"]'
    orientation_xpath = '//input[@class="single-input"]'
    add_xpath = '//input[@class="single-input"]'
    select_add_xpath = '//ul[@class="dropdown"]/li[1]'
    putout_xpath = '//button[@class="css-k3hpu2 css-osq2ks dyn publishBtn red"]'
    try:
        # 这里需要写一下你短视频存储的绝对路径
        filename = "C:/videos/" + task_config['video_url'].split('/')[-1]
        # 把cookie解析出来
        cooklist = json.loads(task_config["account_cookie"])
        # 设置浏览器的url
        driver.get(xhs_url)
        # 清除浏览器cookie
        driver.delete_all_cookies()
        # 循环把cookie设置到浏览器中
        for i in cooklist:
            driver.add_cookie(i)

        # 再设置一下浏览器的url
        driver.get(pub_url)
        # 等待5秒开始等待网页完整打开
        time.sleep(5)
        # 验证cookie是否过期
        if driver.execute_script("return document.location.href") == (xhs_url + "?redirectReason=401"):
            print("cookie过期")
            return False
        # 下载视频文件到本地
        file_download(task_config['video_url'], filename)
        # 判断上传按钮是否存在，存在即上传文件
        if wait_elm(driver, 60, upload_xpath):
            driver.find_element(By.XPATH, upload_xpath).send_keys(filename)
        else:
            print("没有找到上传按钮")
            return False
        
        # 输入标题
        if task_config["title"]:
            if wait_elm(driver, 15, title_xpath):
                driver.find_element(By.XPATH, title_xpath).send_keys(task_config["title"])
        # 输入简介
        if task_config["intro"]:
            if wait_elm(driver, 15, intro_xpath):
                driver.find_element(By.XPATH, intro_xpath).send_keys(task_config["intro"])
        # 滑动屏幕，模拟一下人的操作，这里的500参数最好通过设置成200-1000的随机数
        driver.execute_script('document.documentElement.scrollTop = 500')
        # 判断是否存在地址信息
        if task_config["poi_name"]:
            # 点击选择地址
            if wait_elm(driver, 15, orientation_xpath):
                driver.find_element(By.XPATH, orientation_xpath).click()
            time.sleep(2)
            # 输入地址
            if wait_elm(driver, 15, add_xpath):
                driver.find_element(By.XPATH, add_xpath).send_keys(task_config["poi_name"])
            time.sleep(5)
            # 点击地址
            if wait_elm(driver, 15, select_add_xpath):
                driver.find_element(By.XPATH, select_add_xpath).click()
                # js = f"document.getElementsByClassName('{select_add_xpath.split('class=')[1].split(']')[0]}')[0].getElementsByTagName('{select_add_xpath.split('/')[3][:-3]}')[0].click();"
                # driver.execute_script(js)
            time.sleep(5)
        # 点击发布按钮
        if wait_elm(driver, 120, putout_xpath):
            time.sleep(20)
            try:
                driver.find_element(By.XPATH, putout_xpath).click()
            except:
                js = f"document.getElementsByClassName('css-k3hpu2 css-osq2ks dyn publishBtn red')[0].click();"
                driver.execute_script(js)
            time.sleep(10)
            if driver.execute_script("return document.location.href") == manage_url:
                driver.delete_all_cookies()
                print("检测发布成功")
            else:
                print("发布失败")
                return False
        else:
            print("发布失败")
            return False
        return True
    except Exception as e:
        # 任务失败
        print(e)
        driver.delete_all_cookies()
        return False
    finally:
        try:
            driver.delete_all_cookies()
            os.remove(filename)
        except:
            pass

# 执行函数
def run(jsonText):
    # 防止抖音或者小红书检查rpa指纹，故使用浏览器接管方式模拟操作
    # 思路：使用cmd运行浏览器，然后再用debuggerAddress接管    

    # 浏览器服务端口号
    port = 5050
    # 浏览器用户缓存目录
    user_data_path = f"C:/user_data/{port}"
    # 把这个目录清除一下，避免文件冲突
    shutil.rmtree(user_data_path)
    # 使用命令行启动一个浏览器
    subprocess.Popen(f'C:/Users/tingwu/AppData/Local/Google/Chrome/Application/chrome --remote-debugging-port={port} --user-data-dir="{user_data_path}"')  # --headless --no-startup-window
    print("等待5秒再接管浏览器")
    time.sleep(5)
    option = webdriver.ChromeOptions()
    option.add_experimental_option("debuggerAddress", f"127.0.0.1:{port}")  # 接管浏览器
    driver = webdriver.Chrome(options=option)

    # 把jsonText转换为字典
    publish_task = json.loads(jsonText)
    # 判断是小红书任务还是抖音任务
    if publish_task["app_name"] == 'xhs':
        xhs_publish_server(publish_task, driver)
    elif publish_task["app_name"] == 'dy':
        douyin_publish_server(publish_task, driver)

# 主程序启动
if __name__ == '__main__':
    # 支持多进程执行任务，多进程逻辑自行补充
    # 任务必须的参数，从数据库查或者文件中取请自行实现逻辑
    # 参数说明：
    # task_id：任务ID
    # intro：视频描述
    # title：视频标题
    # app_name：应用名称，dy=抖音，xhs=小红书
    # poi_name：地点名称
    # video_url：视频链接
    # account_cookie: 账号cookie
    jsonText = {
        "task_id": 1,
        "intro": "这里是视频简介描述标题 #话题1 #话题2",
        "title": "",
        "app_name": "dy",
        "poi_name": "",
        "video_url": "http://min.twznai.com/short_video/video/3892/lrJHi9OFZeFxgWdj49zxEERK9kIn.mp4",
        "account_cookie": "[{\"name\": \"galaxy.creator.beaker.session.id\", \"path\": \"/\", \"value\": \"17063333491111188112\", \"domain\": \".xiaohongshu.com\", \"expiry\": 17318149, \"secure\": true, \"httpOnly\": false, \"sameSite\": \"Lax\"}, {\"name\": \"xsecappid\", \"path\": \"/\", \"value\": \"ugc\", \"domain\": \".xiaohongshu.com\", \"expiry\": 1738050078, \"secure\": false, \"httpOnly\": false, \"sameSite\": \"Lax\"}, {\"name\": \"galaxy_creator_session_id\", \"path\": \"/\", \"value\": \"amii0qbMPxfT8dIE2pVHqLY5y9iyoVSIEfew\", \"domain\": \".xiaohongshu.com\", \"expiry\": 1706938149, \"secure\": true, \"httpOnly\": false, \"sameSite\": \"Lax\"}, {\"name\": \"feugc-status\", \"path\": \"/\", \"value\": \"online\", \"domain\": \".creator.xiaohongshu.com\", \"expiry\": 1706600478, \"secure\": false, \"httpOnly\": true, \"sameSite\": \"Lax\"}, {\"name\": \"customerClientId\", \"path\": \"/\", \"value\": \"160569354335444\", \"domain\": \".xiaohongshu.com\", \"expiry\": 1740893349, \"secure\": true, \"httpOnly\": true, \"sameSite\": \"Lax\"}, {\"name\": \"a1\", \"path\": \"/\", \"value\": \"18d49642043fnuup5lkg37dj3533l6xoygdg4p31450000257196\", \"domain\": \".xiaohongshu.com\", \"expiry\": 1737869315, \"secure\": true, \"httpOnly\": false, \"sameSite\": \"Lax\"}, {\"name\": \"webId\", \"path\": \"/\", \"value\": \"57ab513cad9429c425604c8c2aaf829a\", \"domain\": \".xiaohongshu.com\", \"expiry\": 1737869315, \"secure\": true, \"httpOnly\": false, \"sameSite\": \"Lax\"}, {\"name\": \"gid\", \"path\": \"/\", \"value\": \"yYf4jK4JyivdyYf4jK4J8kUq4qiI77U21Tkqfy4WfVq2qk28q1KCl4888J2WyjKJDJJiKi8j\", \"domain\": \".xiaohongshu.com\", \"expiry\": 1741074078, \"secure\": false, \"httpOnly\": false, \"sameSite\": \"Lax\"}, {\"name\": \"access-token-creator.xiaohongshu.com\", \"path\": \"/\", \"value\": \"customer.ares.AT-ccb656f0931c4e2fbea6060b4e511d85-2b9a9b5a728748c3a25db0be1eb5083c\", \"domain\": \".xiaohongshu.com\", \"expiry\": 1737869349, \"secure\": true, \"httpOnly\": true, \"sameSite\": \"Lax\"}, {\"name\": \"customer-sso-sid\", \"path\": \"/\", \"value\": \"65b494a5f600000000000007\", \"domain\": \".xiaohongshu.com\", \"expiry\": 1740893349, \"secure\": true, \"httpOnly\": true, \"sameSite\": \"Lax\"}, {\"name\": \"x-user-id-creator.xiaohongshu.com\", \"path\": \"/\", \"value\": \"64eec3d600000000010058d9\", \"domain\": \".xiaohongshu.com\", \"expiry\": 1740893349, \"secure\": true, \"httpOnly\": true, \"sameSite\": \"Lax\"}, {\"name\": \"feugc-status.sig\", \"path\": \"/\", \"value\": \"B9ChOZWemdxggiLg0HsBku4anQtV-2bl8oVorYSDQEw\", \"domain\": \".creator.xiaohongshu.com\", \"expiry\": 1706600478, \"secure\": false, \"httpOnly\": true, \"sameSite\": \"Lax\"}]"
    }
    run(jsonText)